欸,今天我們不講 AI,不講 prompt,也不講「如何讓 LLM 幫你賺第一桶金」。
我們要講一個更古老、更穩定、也更容易讓你懷疑人生的東西:SQL。
你以為資料分析師在做什麼?
大部分時間,他們都在對資料庫講情話。
講到一半出現 error,接著開始罵人,罵完又回來改 query。
最後那句「GROUP BY」不只是 SQL 語法,而是工程師對人生的註解:
——「把一切都分組起來,這樣崩潰起來才有條理。」
目前 schema
我們的世界觀長這樣 👇
四張表,像四個國家,各自有自己的性格、煩惱、和奇怪的外掛。
| 表名 | 角色定位 |
|---|---|
papers |
論文資料庫,知識的倉庫,裡面塞滿 PDF、作者名,還有那種一句看不懂的標題 |
user_sent_papers |
Email pipeline 發送紀錄——你的論文快遞員 |
chat_history |
對話紀錄。AI 和人互相傷害的證據。 |
user_setting |
使用者設定。某種程度上,就是「人類情緒的 config 檔」。 |
如果用人格化比喻:
這四個湊在一起,像 Slack 裡那種奇怪的工作小組,吵歸吵,但少一個都不行。
先閉上眼睛想一下,如果這些資料變得有趣,你就有成為資料分析師的潛力了。
如果不有趣…嗯,也讓我們繼續看下去 ☕️
(我知道這段你可能會想跳過,但拜託看一下,這就是我們分析的世界地圖。)
CREATE TABLE users (
id VARCHAR(255) PRIMARY KEY,
last_query_date DATE,
total_queries INTEGER NOT NULL DEFAULT 0,
remaining_tokens INTEGER NOT NULL DEFAULT 1000
);
CREATE TABLE user_setting (
user_id VARCHAR(255) PRIMARY KEY,
user_language VARCHAR(255) NOT NULL,
translate BOOLEAN NOT NULL DEFAULT FALSE,
system_prompt TEXT NOT NULL DEFAULT '',
top_k INTEGER NOT NULL DEFAULT 5,
use_rag BOOLEAN NOT NULL DEFAULT TRUE,
subscribe_email BOOLEAN NOT NULL DEFAULT FALSE,
reranker_enabled BOOLEAN NOT NULL DEFAULT TRUE,
temperature FLOAT NOT NULL DEFAULT 0.6, -- LLM temperature
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE TABLE chat_history (
id SERIAL PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
input TEXT NOT NULL,
output TEXT NOT NULL,
input_token INT,
output_token INT,
latency_ms INT, -- 延遲時間
model VARCHAR(64),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE TABLE papers (
id SERIAL PRIMARY KEY,
arxiv_id VARCHAR(32) UNIQUE NOT NULL,
title TEXT NOT NULL,
authors TEXT [] NOT NULL,
abstract TEXT,
categories TEXT [],
published_date DATE,
updated_date DATE,
pdf_url TEXT,
-- Parsed PDF content
raw_text TEXT,
sections JSON,
"references" JSON,
-- PDF processing metadata
parser_used VARCHAR,
parser_metadata JSON,
pdf_processed BOOLEAN NOT NULL DEFAULT FALSE,
pdf_processing_date TIMESTAMP,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE user_sent_papers (
id SERIAL PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
arxiv_id VARCHAR NOT NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
好,這一串如果你沒被嚇跑,恭喜,你比我還有毅力。
這就是一個「AI 助理平台」的資料後台骨架。
簡單說,它記錄了:
基本上,人生縮影。
——大家都在講話,但不是每個人都會回信。
單表分析就像一個人打副本。
沒有隊友,沒有救援,只有你和冷冰冰的資料庫。
但這樣也好,至少 SQL 不會偷你的午餐。
| 表 | 核心分析 | SQL 範例 |
|---|---|---|
papers |
論文分類統計 | SELECT category, COUNT(*) FROM papers |
user_sent_papers |
每日發送量、每位使用者收到的論文數 | GROUP BY day / user_id |
chat_history |
每位使用者總對話數、每日對話量、平均延遲與 token 使用量 | GROUP BY user_id / day |
papers:知識的墳場(我說倉庫)SELECT category, COUNT(*) AS paper_count
FROM papers, unnest(categories) AS category
GROUP BY category
ORDER BY paper_count DESC;
| category | paper_count |
|---|---|
| cs.AI | 56 |
| cs.LG | 23 |
| cs.CL | 18 |
這結果很合理。AI 人寫最多的還是 AI。
就像設計師畫海報永遠加字體陰影一樣:明知道不該,但總是忍不住。
user_sent_papers:Email 外送員的生死戰SELECT DATE(sent_at) AS day, COUNT(*) AS sent_count
FROM user_sent_papers
GROUP BY day
ORDER BY day;
| day | sent_count |
|---|---|
| 2025-09-23 | 2 |
| 2025-09-24 | 3 |
五封信,感覺不多,但這世界上能把郵件寄出去又不被擋 spam 的人,已經值得敬禮。
SELECT user_id, COUNT(*) AS sent_paper_count
FROM user_sent_papers
GROUP BY user_id
ORDER BY sent_paper_count DESC;
| user_id | sent_paper_count |
|---|---|
| CCCCqBBCCDD85853WTWWNNNHJI22 | 5 |
| gradio user | 55 |
81 次對話,聽起來像「分手後還在找 AI 傾訴」的使用者。
AI:「你今天想聊什麼?」
使用者:「我前任…」
SQL:「SELECT * FROM trauma WHERE resolved = false;」
SELECT user_id, COUNT(*) AS total_chats
FROM chat_history
GROUP BY user_id
ORDER BY total_chats DESC;
| user_id | total_chats |
|---|---|
| CCCCqBBCCDD85853WTWWNNNHJI22 | 81 |
| gradio user | 55 |
SELECT DATE(created_at) AS day, COUNT(*) AS chat_count
FROM chat_history
GROUP BY day
ORDER BY day;
| day | chat_count |
|---|---|
| 2025-09-22 | 73 |
| 2025-09-23 | 61 |
| 2025-09-24 | 2 |
SELECT
user_id,
ROUND(AVG(latency_ms), 2) AS avg_latency_ms,
ROUND(AVG(input_token), 2) AS avg_input_token,
ROUND(AVG(output_token), 2) AS avg_output_token
FROM chat_history
GROUP BY user_id
ORDER BY avg_latency_ms DESC;
| user_id | avg_latency_ms | avg_input_token | avg_output_token |
|---|---|---|---|
| CCCCqBBCCDD85853WTWWNNNHJI22 | 4216.80 | 303.78 | 104.38 |
| gradio user | 1721.29 | 626.24 | 272.07 |
4200ms 平均延遲?
那不是延遲,那是修行。
在這期間,你可以泡咖啡、檢查人生選擇、順便懷疑自己是不是忘了加索引。
單挑夠了,該組隊了。
跨表分析就像開會,一堆人講話但沒人聽懂對方。
SQL 就是那個冷靜的記錄員。
| 分析主題 | SQL 目標 | 說明 |
|---|---|---|
| 使用者對話量 vs 收到論文數 | LEFT JOIN chat_history + user_sent_papers |
找出活躍使用者是否也收到大量論文 |
| 高互動但未訂閱 Email | 過濾 COUNT(ch.id) > 50 & subscribe_email = FALSE |
潛在促銷對象 |
| RAG 功能使用情況 vs 對話量 | chat_history + user_setting |
了解高互動使用者是否有開 RAG |
| 查詢論文是否被 Email 發送 | chat_history + papers + user_sent_papers |
了解 RAG pipeline 查詢結果轉化為 Email 的比例 |
| 被最多使用者查詢與發送的論文 | papers + chat_history + user_sent_papers |
找出最熱門的論文 |
| 使用者漏斗分析 | users + chat_history + user_sent_papers + user_setting |
從註冊 → 對話 → 收到論文 → 訂閱 Email 的流失分析 |
可以找出:活躍聊天使用者是否也收到了大量論文。
SELECT
u.id AS user_id,
COUNT(DISTINCT ch.id) AS total_chats,
COUNT(DISTINCT usp.arxiv_id) AS total_sent_papers
FROM users u
LEFT JOIN chat_history ch ON u.id = ch.user_id
LEFT JOIN user_sent_papers usp ON u.id = usp.user_id
GROUP BY u.id
ORDER BY total_chats DESC;
| user_id | total_chats | total_sent_papers |
|---|---|---|
| lZCCqBoAJDM85JV3ZTWTDWNHije2 | 81 | 5 |
| gradio user | 55 | 0 |
這畫面很像辦公室裡那種人:
講最多的,文件最少。
找出高互動但沒有訂閱 Email 的潛在促銷對象。
SELECT
u.id AS user_id,
COUNT(ch.id) AS total_chats,
COALESCE(SUM(CASE WHEN usp.id IS NOT NULL THEN 1 ELSE 0 END), 0) AS sent_count,
us.subscribe_email
FROM users u
LEFT JOIN chat_history ch ON u.id = ch.user_id
LEFT JOIN user_sent_papers usp ON u.id = usp.user_id
LEFT JOIN user_setting us ON u.id = us.user_id
GROUP BY u.id, us.subscribe_email
HAVING COUNT(ch.id) > 50 AND (us.subscribe_email = FALSE OR us.subscribe_email IS NULL)
ORDER BY total_chats DESC;
| user_id | total_chats | sent_count | subscribe_email |
|---|---|---|---|
| gradio user | 55 | 0 |
這種人現實裡也常見:
跟你聊超多,但從不 follow。
社群媒體叫這種人「已讀狂魔」,CRM 叫他「潛在客戶」。
可以評估 RAG 功能是否被高互動使用者使用,幫助優化 pipeline。
SELECT
u.id AS user_id,
COUNT(ch.id) AS total_chats,
us.use_rag,
us.reranker_enabled
FROM users u
LEFT JOIN chat_history ch ON u.id = ch.user_id
LEFT JOIN user_setting us ON u.id = us.user_id
GROUP BY u.id, us.use_rag, us.reranker_enabled
ORDER BY total_chats DESC;
| user_id | total_chats | use_rag | reranker_enabled |
|---|---|---|---|
| lZCCqBoAJDM85JV3ZTWTDWNHije2 | 81 | false | false |
| gradio user | 55 | false | false |
兩位都沒開 RAG。
也就是說——我們花了一週做的功能,沒人用。
工程師的眼淚,掉進 NULL 裡。
用來分析:使用者在 RAG pipeline 查詢的論文,有多少實際被發送 Email。
SELECT
ch.user_id,
ch.id AS chat_id,
COALESCE(ARRAY_AGG(DISTINCT p.arxiv_id) FILTER (WHERE p.id IS NOT NULL), '{}') AS queried_papers,
COALESCE(
(SELECT ARRAY_AGG(usp.arxiv_id)
FROM user_sent_papers usp
WHERE usp.user_id = ch.user_id
AND usp.arxiv_id IN (
SELECT p2.arxiv_id
FROM papers p2
WHERE ch.input ILIKE '%' || p2.title || '%'
)
), '{}'
) AS sent_papers
FROM chat_history ch
LEFT JOIN papers p ON ch.input ILIKE '%' || p.title || '%'
GROUP BY ch.user_id, ch.id
ORDER BY ch.user_id, ch.id;
| user_id | chat_id | queried_papers | sent_papers |
|---|---|---|---|
| gradio user | 74 | {2509.15151v1} | {} |
| gradio user | 75 | {2509.15151v1} | {} |
| gradio user | 137 | {2509.17998v1} | {} |
| gradio user | 138 | {2509.17998v1} | {} |
| lZCCqBoAJDM85JV3ZTWTDWNHije2 | 10 | {} | {} |
| lZCCqBoAJDM85JV3ZTWTDWNHije2 | 11 | {} | {} |
| lZCCqBoAJDM85JV3ZTWTDWNHije2 | 34 | {} | {} |
查了,但沒寄。
有點像 Tinder:配對到了,沒講話。
資料分析有時候不殘酷,它只是誠實。
找出最受關注的論文,分析 RAG pipeline 與 Email pipeline 的重疊。
SELECT
p.arxiv_id,
p.title,
COUNT(DISTINCT ch.user_id) AS queried_by_users,
COUNT(DISTINCT usp.user_id) AS sent_to_users
FROM papers p
LEFT JOIN chat_history ch ON ch.input ILIKE '%' || p.title || '%'
LEFT JOIN user_sent_papers usp ON p.arxiv_id = usp.arxiv_id
GROUP BY p.arxiv_id, p.title
ORDER BY queried_by_users DESC, sent_to_users DESC
LIMIT 20;
| arxiv_id | title | queried_by_users | sent_to_users |
|---|---|---|---|
| 2509.15057v1 | Balancing Sparse RNNs with Hyperparameterization Benefiting¶ Meta-Learning | 1 | 0 |
| 2509.15167v1 | Semi-Supervised 3D Medical Segmentation from 2D Natural Images¶ Pretrained Model | 1 | 0 |
| 2509.15151v1 | Exploring How Audio Effects Alter Emotion with Foundation Models | 1 | 0 |
| 2509.17998v1 | Adaptive Kernel Design for Bayesian Optimization Is a Piece of CAKE with¶ LLMs | 1 | 0 |
| 2509.15116v1 | The mechanization of science illustrated by the Lean formalization of¶ the multi-graded Proj construction | 0 | 1 |
| 2509.18046v1 | HuMam: Humanoid Motion Control via End-to-End Deep Reinforcement¶ Learning with Mamba | 0 | 1 |
| 2509.18060v1 | TMD-TTS: A Unified Tibetan Multi-Dialect Text-to-Speech Synthesis for¶ Ü-Tsang, Amdo and Kham Speech Dataset Generation | 0 | 1 |
| 2509.16179v1 | Fast OTSU Thresholding Using Bisection Method | 0 | 1 |
| 2509.15156v1 | Leveraging Geometric Visual Illusions as Perceptual Inductive Biases for¶ Vision Models | 0 | 1 |
| 2509.15174v1 | SMARTER: A Data-efficient Framework to Improve Toxicity Detection with¶ Explanation via Self-augmenting Large Language Models | 0 | 0 |
最熱門的一篇,也只有一個人查。
這平台的社交狀況,大概跟我一樣冷清。
看到「總使用者 → 對話使用者 → 收到論文 → 訂閱 Email」的流失情況
SELECT
COUNT(*) AS total_users,
COUNT(DISTINCT CASE WHEN has_chat THEN id END) AS chat_users,
COUNT(DISTINCT CASE WHEN has_sent THEN id END) AS emailed_users,
COUNT(DISTINCT CASE WHEN subscribe_email THEN id END) AS subscribed_users,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN has_chat THEN id END) / COUNT(*), 1) || '%' AS pct_chat_users,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN has_sent THEN id END) / COUNT(*), 1) || '%' AS pct_emailed_users,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN subscribe_email THEN id END) / COUNT(*), 1) || '%' AS pct_subscribed_users
FROM (
SELECT
u.id,
us.subscribe_email,
EXISTS(SELECT 1 FROM chat_history ch WHERE ch.user_id = u.id) AS has_chat,
EXISTS(SELECT 1 FROM user_sent_papers usp WHERE usp.user_id = u.id) AS has_sent
FROM users u
LEFT JOIN user_setting us ON u.id = us.user_id
) t;
| total_users | chat_users | emailed_users | subscribed_users | pct_chat_users | pct_emailed_users | pct_subscribed_users |
|---|---|---|---|---|---|---|
| 100 | 80 | 60 | 50 | 80% | 60% | 50% |
80% 會聊天,60% 收信,50% 訂閱。
比我在交友軟體的轉換率高多了。
寫 SQL 的過程,就像在面對現實。
你以為只是「JOIN 幾個表」,結果 JOIN 出來的,是人性。
有些人話多但不行動。
有些人行動快但不回頭。
有些人只是靜靜地存在 database 裡,從未被 select 過。
但這樣也好。
資料不會騙你,只會讓你更懂這個世界。
哪怕懂得越多,越想重灌人生。
📚 總結一句話:
SQL 就像感情——寫錯一句,結果全歪。
但寫對的那一刻,真的會有點魔法的感覺。